/*
 * $Id: QEExchangeElement.m 248 2008-09-08 15:31:38Z jon $
 * Sleeper
 *
 * Created by Jon Gordon on 7/24/08
 * Copyright Jon Gordon.  All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining 
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including 
 * without limitation the rights to use, copy, modify, merge, publish, 
 * distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to 
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


#import <Cocoa/Cocoa.h>
#import <limits.h>

#import "QEExchangeElement.h"

#import "QECallsign.h"
#import "QENSStringExtension.h"


static const unsigned int BAND_160M_LO =  1800000;
static const unsigned int BAND_160M_HI =  2000000;
static const unsigned int BAND_80M_LO  =  3600000;
static const unsigned int BAND_80M_HI  =  4000000;
static const unsigned int BAND_40M_LO  =  7125000;
static const unsigned int BAND_40M_HI  =  7300000;
static const unsigned int BAND_20M_LO  = 14150000;
static const unsigned int BAND_20M_HI  = 14350000;
static const unsigned int BAND_15M_LO  = 21200000;
static const unsigned int BAND_15M_HI  = 21450000;
static const unsigned int BAND_10M_LO  = 28300000;
static const unsigned int BAND_10M_HI  = 29700000;

static const unsigned int SCALING_FACTOR = 10;
static const unsigned int KILO_SCALING_FACTOR = 1000;
static const unsigned int KILO_DIGITS_LENGTH = 3;   // in decimal, multiplying by one thousand adds 3 digits
static const unsigned int MEGA_DIGITS_LENGTH = 6;   // that is, 2 * KILO_DIGITS_LENGTH

static NSString *KEY_CALLSIGN;
static NSString *KEY_CHECK;
static NSString *KEY_FREQUENCY;
static NSString *KEY_PRECEDENCE;
static NSString *KEY_SECTION;
static NSString *KEY_SERIAL_RCVD;

static NSArray *keys;
static NSMutableDictionary *createdElements;

static NSPredicate *scaledFrequencyPattern;
static NSPredicate *unscaledFrequencyPattern;
static NSPredicate *serialPattern;
static NSPredicate *wholeNumberPattern;

static NSCharacterSet *zeroSet;
static NSCharacterSet *digitSet;
static NSCharacterSet *precedenceSet;

static NSSet *sectionSet;

//
// QEExchangeElement is currently implemented as a class cluster, with
// a single private subclass for each element.  Moreover, each element's
// subclass is a singleton, because there's no state in any instance.
//
// The interface definitions below are for the private subclasses.
//

@interface QECallElement : QEExchangeElement
@end

@interface QECheckElement : QEExchangeElement
@end

@interface QEFreqElement : QEExchangeElement
- (unsigned int)frequencyInHertzFromString:(NSString *)src;
@end

@interface QEPrecElement : QEExchangeElement
@end

@interface QESectionElement : QEExchangeElement
@end

@interface QESerialRcvdElement : QEExchangeElement
@end

@interface QEExchangeElement (Private)
+ (id)allocNamedElement:(NSString *)name
               withZone:(NSZone *)zone;
@end

@implementation QECallElement : QEExchangeElement
+ (id)allocWithZone:(NSZone *)zone
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [super allocNamedElement:KEY_CALLSIGN withZone:zone];
} // allocWithZone

+ (void)initialize
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        //NSLog(@"initializing QECallElement");
    } // if
} // initialize

- (NSString *)elementName
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return KEY_CALLSIGN;
} // elementName


- (BOOL)isLegalValueInString:(NSString *)stringValue
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [QECallsign isValidCallsign:stringValue];
} // isLegalValueInString:
@end


@implementation QECheckElement : QEExchangeElement
+ (id)allocWithZone:(NSZone *)zone
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [super allocNamedElement:KEY_CHECK withZone:zone];
} // allocWithZone

+ (void)initialize
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        //NSLog(@"initializing QECheckElement");
        digitSet = [NSCharacterSet decimalDigitCharacterSet];
    } // if
} // initialize

- (NSString *)elementName
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return KEY_CHECK;
} // elementName


- (BOOL)isLegalValueInString:(NSString *)stringValue
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (nil == stringValue)
        return NO;
    if (0 == [stringValue length])
        return NO;
    
    return (([stringValue length] == 2) &&
            ([digitSet characterIsMember:[stringValue characterAtIndex:0]]) &&
            ([digitSet characterIsMember:[stringValue characterAtIndex:1]]));
} // isLegalValueInString:


- (id)valueforString:(NSString *)stringValue
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSAssert1([self isLegalValueInString:stringValue],
              @"illegal value passed as source for check element: \"%@\"",
              stringValue);
    return [NSNumber numberWithInt:[stringValue intValue]];
} // valueForString:
@end


@implementation QEFreqElement : QEExchangeElement
+ (id)allocWithZone:(NSZone *)zone
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [super allocNamedElement:KEY_FREQUENCY withZone:zone];
} // allocWithZone

+ (void)initialize
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        //NSLog(@"initializing QEFreqElement");
        unscaledFrequencyPattern = [[NSPredicate predicateWithFormat:@"self matches %@", @"^0*[1-9]\\d*$"] retain];
        scaledFrequencyPattern   = [[NSPredicate predicateWithFormat:@"self matches %@", @"^0*[1-9]\\d*\\.\\d*$"] retain];
        zeroSet = [[NSCharacterSet characterSetWithCharactersInString:@"0"] retain];
    } // if
} // initialize

- (NSString *)elementName
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return KEY_FREQUENCY;
} // elementName


- (BOOL)isLegalValueInString:(NSString *)stringValue
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return (0 != [self frequencyInHertzFromString:stringValue]);
} // isLegalValueInString:


- (id)valueForString:(NSString *)stringValue
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSNumber *result = nil;
    unsigned int value = [self frequencyInHertzFromString:stringValue];
    if (0 != value) {
        result = [NSNumber numberWithDouble:(((double) value) / 1000000.0)];
    } // if    
    return result;
} // valueForString:


- (unsigned int)frequencyInHertzFromString:(NSString *)src {
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if ((nil == src) || ([src length] == 0)) {
        return 0U;
    } // if
    
    unsigned int result = 0U;
    
    if ([unscaledFrequencyPattern evaluateWithObject:src]) {
        //        NSLog(@"  unscaled frequency: %@", src);
        // string is the significant digits of a frequency in hertz;
        // add (insignificant) zeros, trying to scale to a legal range
        if ([[src stringByTrimmingLeadingCharsFromSet:zeroSet] length] <= [@"999999999" length]) {
            unsigned int startValue = [src intValue];
            unsigned int value = startValue;
            //           //NSLog(@"  parsed to value %u", value);
            while (value < BAND_160M_LO) {
                value *= SCALING_FACTOR;
            } // while
            //            NSLog(@"  scaled to value %u", value);
            NSAssert3(((BAND_160M_LO <= value) && (value < BAND_160M_LO * SCALING_FACTOR)) ||
                      (startValue >= BAND_160M_LO * SCALING_FACTOR),
                      @"  value %u is supposed to be in the interval [%u, %u]",
                      value, BAND_160M_LO, BAND_160M_LO * SCALING_FACTOR);
            
            if ( ((BAND_160M_LO <= value) && (value <= BAND_160M_HI)) ||
                ((BAND_80M_LO <= value) && (value <= BAND_80M_HI)) ||
                ((BAND_40M_LO <= value) && (value <= BAND_40M_HI)) ||
                ((BAND_20M_LO <= value) && (value <= BAND_20M_HI)) ) {
                //               //NSLog(@"  scaled value matches a legal band, setting result");
                result = value;
            } else {
                value *= SCALING_FACTOR;
                //               //NSLog(@"  no match on first try, rescaled to %u", value);
                if ( ((BAND_15M_LO <= value) && (value <= BAND_15M_HI)) ||
                    ((BAND_10M_LO <= value) && (value <= BAND_10M_HI)) ) {
                    //                   //NSLog(@"  rescaled value matches a legal band, setting result");
                    result = value;
                } // if
            } // else
        } // if
    } else if ([scaledFrequencyPattern evaluateWithObject:src]) {
        /* src is a number, with a decimal point, expressing a frequency in
         * hertz (unlikely), kilohertz, or megahertz.  We can't just
         * convert to a float or double and start scaling by tens (as above)
         * or thousands, because the binary representation of 1/10 is
         * infinitely repeating.  This means, among other things, that we
         * can't expect scaling to give exact results--we lose precision
         * in the tail end, which means that we can't test numbers for
         * exact equality.
         *
         * We split src at the decimal point into a whole part and a fraction
         * part, trimming leading and trailing zeroes, respectively, to
         * keep them from skewing the result.  We then calculate the magnitude
         * of the whole part and then derive from it the maximum magnitude of
         * the fraction part.  (Remember, we a have float in Hz, kHz, or MHz,
         * and we want a precise value, in Hz, in an unsigned int.)  Finally,
         * we pad the fraction part with zeroes to make it exactly the right
         * magnitude, concatenate the strings, and parse to an integer.
         */
        //        NSLog(@"  scaled frequency: %@", src);
        NSArray *numberParts = [src componentsSeparatedByString:@"."];
        NSAssert1(([numberParts count] == 2),
                  @"can't split scaled frequency \"%@\"",
                  src);
        NSString *wholePart = [[numberParts objectAtIndex:0] stringByTrimmingLeadingCharsFromSet:zeroSet];
        NSString *fractionPart = [[numberParts objectAtIndex:1] stringByTrimmingTrailingCharsFromSet:zeroSet];
        //       //NSLog(@"  whole part: \"%@\"; fraction part \"%@\"", wholePart, fractionPart);
        
        unsigned int wholePartValue = 0;
        unsigned int wholePartLength = 0;
        unsigned int wholePartUsedDigits = 0;
        unsigned int fractionPartValue = 0;
        unsigned int fractionPartLength = 0;
        unsigned int maxFractionPartLength = 0;
        
        if (0 == (wholePartLength = [wholePart length])) {
            wholePartValue = 0;
        } else {
            wholePartValue = [wholePart intValue];
        } // else
        //       //NSLog(@"  whole part value: %u; whole part length %u", wholePartValue, wholePartLength);
        
        wholePartUsedDigits = wholePartLength - (wholePartLength % KILO_DIGITS_LENGTH);
        maxFractionPartLength = (wholePartUsedDigits >= MEGA_DIGITS_LENGTH ? 0 : MEGA_DIGITS_LENGTH - wholePartUsedDigits);
        fractionPartLength = [fractionPart length];
        if (fractionPartLength > maxFractionPartLength) {
            fractionPart = [fractionPart substringToIndex:maxFractionPartLength];
            fractionPartLength = maxFractionPartLength;
        } // if
        //       //NSLog(@"  fraction part value: %u; fraction part length: %u", fractionPartValue, fractionPartLength);
        
        if (0 == fractionPartLength) {
            fractionPartValue = 0;
        } else {
            if (0 != (fractionPartLength % KILO_DIGITS_LENGTH)) {
                fractionPartLength += (KILO_DIGITS_LENGTH - (fractionPartLength % KILO_DIGITS_LENGTH));
                fractionPart = [fractionPart stringByPaddingToLength:fractionPartLength
                                                          withString:@"0"
                                                     startingAtIndex:0];
            } // if
            fractionPartValue = [fractionPart intValue];
        } // else
        //       //NSLog(@"  whole part: \"%@\"; value = %u", wholePart, wholePartValue);
        //       //NSLog(@"  fraction part: \"%@\"; length: %u; value = %u", fractionPart, fractionPartLength, fractionPartValue);
        
        unsigned int value = 0;
        if ((0 != wholePartValue) || (0 != fractionPartValue)) {
            if (0 != fractionPartValue) {
                value = [[wholePart stringByAppendingString:fractionPart] intValue];
            } else {
                value = wholePartValue;
            } // else
        } // if
        
        if (value > 0) {
            while (value < BAND_160M_LO) {
                value *= KILO_SCALING_FACTOR;;
            } // while
            if ( ((BAND_160M_LO <= value) && (value <= BAND_160M_HI)) ||
                ((BAND_80M_LO <= value) && (value <= BAND_80M_HI)) ||
                ((BAND_40M_LO <= value) && (value <= BAND_40M_HI)) ||
                ((BAND_20M_LO <= value) && (value <= BAND_20M_HI)) ||
                ((BAND_15M_LO <= value) && (value <= BAND_15M_HI)) ||
                ((BAND_10M_LO <= value) && (value <= BAND_10M_HI)) ) {
                result = value;
            } // if
        } // if
    } // else
    
    return result;
} // frequencyInHertzFromString:
@end


@implementation QEPrecElement : QEExchangeElement
+ (id)allocWithZone:(NSZone *)zone
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [super allocNamedElement:KEY_PRECEDENCE withZone:zone];
} // allocWithZone

+ (void)initialize
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        precedenceSet = [[NSCharacterSet characterSetWithCharactersInString:@"QqAaBbUuMmSs"] retain];
    } // if
} // initialize

- (NSString *)elementName
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return KEY_PRECEDENCE;
} // elementName

- (BOOL)isLegalValueInString:(NSString *)stringValue
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (nil == stringValue)
        return NO;
    if (0 == [stringValue length])
        return NO;
    
    BOOL result = NO;
    if (([stringValue length] == 1) &&
        ([precedenceSet characterIsMember:[stringValue characterAtIndex:0]])) {
        result = YES;
    } // if
    return result;
} // isLegalValueInString:
@end


@implementation QESectionElement : QEExchangeElement
+ (id)allocWithZone:(NSZone *)zone
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [super allocNamedElement:KEY_SECTION withZone:zone];
} // allocWithZone

+ (void)initialize
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        //NSLog(@"initializing QESectionElement");
        
        // ARRL/RAC sections, included in November Sweepstakes exchange
        NSArray *sections = [[[NSArray alloc] initWithObjects:@"CT", @"EMA", @"ME", @"NH", @"RI", @"VT", @"WMA",
                              @"ENY", @"NLI", @"NNJ", @"NNY", @"SNJ", @"WNY",
                              @"DE", @"EPA", @"MDC", @"WPA",
                              @"AL", @"GA", @"KY", @"NC", @"NFL", @"SC", @"SFL", @"WCF", @"TN", @"VA", @"PR", @"VI",
                              @"AR", @"LA", @"MS", @"NM", @"NTX", @"OK", @"STX", @"WTX",
                              @"EB", @"LAX", @"ORG", @"SB", @"SCV", @"SDG", @"SF", @"SJV", @"SV", @"PAC",
                              @"AZ", @"EWA", @"ID", @"MT", @"NV", @"OR", @"UT", @"WWA", @"WY", @"AK",
                              @"MI", @"OH", @"WV",
                              @"IL", @"IN", @"WI",
                              @"CO", @"IA", @"KS", @"MN", @"MO", @"NE", @"ND", @"SD",
                              @"MAR", @"NL", @"QC", @"ON", @"MB", @"SK", @"AB", @"BC", @"NT",
                              nil] autorelease];
        sectionSet = [[NSSet alloc] initWithArray:sections];
    } // if
} // initialize

- (NSString *)elementName
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return KEY_SECTION;
} // elementName

- (BOOL)isLegalValueInString:(NSString *)stringValue
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (nil == stringValue)
        return NO;
    if (0 == [stringValue length])
        return NO;
    
    BOOL result = [sectionSet containsObject:[stringValue uppercaseString]];
    return result;
} // isLegalValueInString:
@end


@implementation QESerialRcvdElement : QEExchangeElement
+ (id)allocWithZone:(NSZone *)zone
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [super allocNamedElement:KEY_SERIAL_RCVD withZone:zone];
} // allocWithZone

+ (void)initialize
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        //NSLog(@"initializing QESerialRcvdElement");
        serialPattern = [[NSPredicate predicateWithFormat:@"self matches %@", @"^[nN][rR]?0*[1-9]\\d*$"] retain];
        wholeNumberPattern = [[NSPredicate predicateWithFormat:@"self matches %@", @"^0*[1-9]\\d*$"] retain];
    } // if
} // initialize

- (NSString *)elementName
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return KEY_SERIAL_RCVD;
} // elementName

- (BOOL)isLegalValueInString:(NSString *)stringValue
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (nil == stringValue)
        return NO;
    if (0 == [stringValue length])
        return NO;
    
    /*
     * The 2006 phone Sweepstakes winner had about 2200 QSOs.  For a naked
     * number that's greater than or equal to 3600, assume it's a frequency,
     * not a serial number.
     */
    const int SERIAL_RCVD_LIMIT = 3599;
    BOOL result;
    if ([wholeNumberPattern evaluateWithObject:stringValue]) {
        int intValue = [stringValue intValue];
        result = (intValue > 0) && (intValue <= SERIAL_RCVD_LIMIT);
    } else {
        result = [serialPattern evaluateWithObject:stringValue];
    } // else
    
    return result;
} // isLegalValueInString:


- (id)valueForString:(NSString *)stringValue
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSNumber *result = nil;
    if ([self isLegalValueInString:stringValue]) {
        unsigned int startIdx = 0;
        unichar c;
        
        // a valid serial number may begin with 'N' or 'NR' --
        // if this one does, strip before converting
        c = [stringValue characterAtIndex:startIdx];
        if (('n' == c) || ('N' == c)) {
            ++startIdx;
            c = [stringValue characterAtIndex:startIdx];
            if (('r' == c) || ('R' == c)) {
                ++startIdx;
            } // if
        } // if
        result = [NSNumber numberWithInt:[[stringValue substringFromIndex:startIdx] intValue]];
    } // if
    return result;
} // valueForString:
@end


//
// Represents one element in a contest exchange--one is configured for
// each element in exchange.
//
@implementation QEExchangeElement
/*
 * Class-wide initializer, invoked by runtime
 */
+ (void)initialize
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        NSLog(@"initializing QEExchangeElement");
        
        KEY_CALLSIGN    = [[NSString stringWithString:@"call"] retain];
        KEY_CHECK       = [[NSString stringWithString:@"check"] retain];
        KEY_FREQUENCY   = [[NSString stringWithString:@"freq"] retain];
        KEY_PRECEDENCE  = [[NSString stringWithString:@"prec"] retain];
        KEY_SECTION     = [[NSString stringWithString:@"section"] retain];
        KEY_SERIAL_RCVD = [[NSString stringWithString:@"serialRcvd"] retain];
        
        keys = [NSArray arrayWithObjects:KEY_CALLSIGN,
                KEY_CHECK,
                KEY_FREQUENCY,
                KEY_PRECEDENCE,
                KEY_SECTION,
                KEY_SERIAL_RCVD,
                nil];
        createdElements = [[NSMutableDictionary alloc] initWithCapacity:[keys count]];
    } // if
} // initialize

/*
 * Each element is a singleton instance of a subclass.  Look to see
 * if one has been created: if so, return it; if not, create it, add it,
 * and then return it.
 */
+ (QEExchangeElement *)elementForName:(NSString *)name
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    
    if (nil == name) {
        NSLog(@"  nil element name, returning nil");
        return nil;         // FUNCTION RETURNS
    } // if
    
    QEExchangeElement *element;
    @synchronized(self) {
        element = [createdElements objectForKey:name];
        if (nil != element) {
            NSLog(@"  instance already exists for element \"%@\"", name);
        } else {
            NSLog(@"  need to create and add instance for element \"%@\"", name);
            if ([KEY_CALLSIGN isEqual:name]) {
                element = [[[QECallElement alloc] init] autorelease];
            } else if ([KEY_CHECK isEqual:name]) {
                element = [[[QECheckElement alloc] init] autorelease];
            } else if ([KEY_FREQUENCY isEqual:name]) {
                element = [[[QEFreqElement alloc] init] autorelease];
            } else if ([KEY_PRECEDENCE isEqual:name]) {
                element = [[[QEPrecElement alloc] init] autorelease];
            } else if ([KEY_SECTION isEqual:name]) {
                element = [[[QESectionElement alloc] init] autorelease];
            } else if ([KEY_SERIAL_RCVD isEqual:name]) {
                element = [[[QESerialRcvdElement alloc] init] autorelease];
            } else {
                NSLog(@"WARNING: \"%@\" not recognized as valid element name", name);
                element = nil;
            } // else
            
            if (nil != element) {
                NSLog(@" for requested element named \"%@\", adding element named \"%@\"", name, [element elementName]);
                [createdElements setObject:element forKey:[element elementName]];
            } // if
        } // else
    } // @synchronized
    
    NSLog(@"    returning element \"%@\"", [element elementName]);
    return element;
} // elementForName:

- (NSString *)elementName
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return @"error";
} // elementName


- (BOOL)isLegalValueInString:(NSString *)stringValue
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return false;
} // isLegalValueInString:


// default implementation, OK for all string-only properties
- (id)valueForString:(NSString *)stringValue
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSString *result = nil;
    if ([self isLegalValueInString:stringValue]) {
        result = [stringValue uppercaseString];
    } // if
    return result;
} // valueForString:


//
// methods overriden to make sure we get only one of each private subclass
//
+ (id)allocWithZone:(NSZone *)zone
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return nil;
}

- (id)copyWithZone:(NSZone *)zone
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return self;
}

- (id)retain
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return self;
}

- (unsigned)retainCount
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return UINT_MAX;  //denotes an object that cannot be released
}

- (void)release
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    //do nothing
}

- (id)autorelease
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return self;
}
@end

@implementation QEExchangeElement (Private)

+ (id)allocNamedElement:(NSString *)name
               withZone:(NSZone *)zone
{
    //NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (nil == [createdElements objectForKey:name]) {
        return [super allocWithZone:zone];
    } // if
    return nil;
}
@end
